package com.zws.cucumber.expander;

import com.zws.cucumber.util.colorfullog.ZwsColorfulLogger;
import com.zws.cucumber.var.ZwsVar;
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;

import java.util.*;
import java.util.stream.Collectors;

import static com.zws.cucumber.expander.ZwsStrConst.*;
import static com.zws.cucumber.expander.ZwsStrParseUtil.isKeyCharacter;
import static com.zws.cucumber.util.colorfullog.ZwsColorfulLogHelper.blue;

/**
 * author: zws
 */
public class ZwsStrTemplateExpander extends ZwsStrExpander {

    private static final Logger logger = ZwsColorfulLogger.of(ZwsStrTemplateExpander.class);

    private final Map<String, Object> actualArgumentMap;
    private final Collection<String> needResolveArguments;

    private final boolean needResolve;

    public static String expandTemplate(String template, ZwsStrBasicExpander parent, boolean debug, int level, Map<String, Object> argumentMap, Collection<String> pendingResolutions) {
        try {
            ZwsStrTemplateExpander expander = new ZwsStrTemplateExpander(template, parent, debug, level, argumentMap, pendingResolutions);
            String output = expander.expand();
            if (!Objects.equals(template, output)
                    && (parent == null || parent.debug || debug)) {
                int level0 = expander.level() + 1;
                if (template.contains("\n") || template.contains("\r") || output.contains("\n") || output.contains("\r")) {
                    logger.debug("expand template <b>#{} </b>\n<b>[</b>\n{}\n<b>] --------> [</b>\n{}\n<b>]</b>",
                        level0, template, output);
                } else {
                    logger.debug("expand template <b>#{} [</b>{}<b>] --> [</b>{}<b>]</b>", level0, template, output);
                }
            }
            return output;
        } catch (Throwable t) {
            logger.debug("expand template <r>[</r>{}<r>]</r> with arguments: <r>{}</r>", template, argumentMap.toString());
            throw t;
        }
    }

    public ZwsStrTemplateExpander(String input, ZwsStrBasicExpander parent, boolean debug, int level, Map<String, Object> actualArgumentMap, Collection<String> needResolveArguments) {
        super(input, parent, debug, level, actualArgumentMap);
        this.actualArgumentMap = actualArgumentMap;
        this.needResolveArguments = needResolveArguments;
        this.needResolve = needResolveArguments != null;
    }

    public String expand() {
        parseOption();
        StringBuilder outputBuf = new StringBuilder();
        while (index < length) {
            if (expectCurrent(SYMBOL_AT)
                    && expectNext(SYMBOL_BRACKETS_LEFT)) {
//                if (debug) { logger.debug("expand template, start to resolve variable, index: {}", index); }
//                String value = resolve();
//                if (debug) { logger.debug("expand template, finish to resolve variable, value: {}", value); }
                outputBuf.append(resolve());

            } else {
                outputBuf.append(charAt(index++));
            }
        }
        return prettyPrintIfNeeded(outputBuf.toString());
    }

    private Object resolve() {
        increaseLevel();
        markStartIndex();
        try {
            return resolve0();
        } catch (Exception e) {
            throw new RuntimeException(getExceptionPositionMessage(), e);
        } finally {
            unmarkStartIndex();
            decreaseLevel();
        }
    }

    private Object resolve0() {
        StringBuilder keyBuf = new StringBuilder();
        StringBuilder defaultBuf = null;
        final int startIndex = index;
        boolean debug = this.debug;
        List<Object> argList = null;
        match(SYMBOL_AT);
        match(SYMBOL_BRACKETS_LEFT);
        skipWhiteSpaces();
        if (expectCurrent(SYMBOL_EXCLAMATION)) {
            debug = true;
            match(SYMBOL_EXCLAMATION);
        }
        skipWhiteSpaces();
        if (isKeyCharacter(charAt(index))) {
            while (isKeyCharacter(charAt(index))) {
                keyBuf.append(charAt(index++));
            }
        } else {
            throw new IllegalArgumentException("Syntax of variable is invalid. Parse info stack: \n" + generateParseStack());
        }
        String key = keyBuf.toString();
        if (debug) { logger.debug("{}, key: {}{}{}", label(key), blue("["), keyBuf, blue("]")); }
        skipWhiteSpaces();

        if (expectCurrent(SYMBOL_QUESTION)) {
            if (debug) { logger.debug("{}, have default value", label(key)); }
            match(SYMBOL_QUESTION);
            defaultBuf = new StringBuilder();
            while (!expectCurrent(SYMBOL_BRACKETS_RIGHT)) {
                defaultBuf.append(charAt(index++));
            }
            if (debug) { logger.debug("{}, default value: {}{}{}", label(key), blue("["), defaultBuf.toString().trim(), blue("]")); }

        } else if (expectCurrent(SYMBOL_SEMICOLON)) {
            if (debug) { logger.debug("{}, have arguments", label(key)); }
            argList = new ArrayList<>();
//            state.setSpec(S_SPEC_TEMPLATE);
            match(SYMBOL_SEMICOLON);
            skipWhiteSpaces();
            argList.add(scanAndExpandOrderedArgument(debug));
            skipWhiteSpaces();
            while (expectCurrent(SYMBOL_COMMA)) {
                match(SYMBOL_COMMA);
                skipWhiteSpaces();
                argList.add(scanAndExpandOrderedArgument(debug));
                skipWhiteSpaces();
            }
            if (debug) { logger.debug("{}, arguments resolved. {}{}{}", label(key), blue("["), argList.stream().map(o -> o.getClass().getSimpleName() + ":" + o).collect(Collectors.joining(", ")), blue("]")); }
        }
        match(SYMBOL_BRACKETS_RIGHT);
        // return if actual argument exists
        if (actualArgumentMap.containsKey(key)) {
            if (CollectionUtils.isNotEmpty(argList)) {
                String template = actualArgumentMap.get(key).toString();
                if (debug) { logger.debug("{}, get template from argument, {}{}{}", label(key), blue("value: ["), template, blue("]")); }
                Map<String, Object> argMap = new HashMap<>();
                for (int i = 0; i < argList.size(); i++) {
                    argMap.put(Integer.toString(i), argList.get(i));
                }
                return expandTemplate(template, this, debug, 0, argMap, null);
            }
            String value = actualArgumentMap.get(key).toString();
            if (debug) { logger.debug("{}, return value from argument, {}{}{}", label(key), blue("value: ["), value, blue("]")); }
            return value;
        }
        // return if default value exists
        if (defaultBuf != null) {
            if (debug) { logger.debug("{}, return value from default value", label(key)); }
            return defaultBuf.toString().trim();
        }
        // NOTES: Backward Compatibility is not promised if you use template mechanism to ref variables
        if (CollectionUtils.isNotEmpty(argList) && ZwsVar.containsKey(key)) {
            String template = ZwsVar.get(key);
            if (debug) { logger.debug("{}, get template from variables, {}{}{}", label(key), blue("value: ["), template, blue("]")); }
            Map<String, Object> argMap = new HashMap<>();
            for (int i = 0; i < argList.size(); i++) {
                argMap.put(Integer.toString(i), argList.get(i));
            }
            return expandTemplate(template, this, debug, 0, argMap, null);
        }
        // return what it is if no need to resolve
        if (!needResolve || !needResolveArguments.contains(key)) {
            if (debug) { logger.debug("{}, return original input when no need to resolve variable", label(key)); }
            return input.substring(startIndex, index);
        }
        throw new IllegalArgumentException("Variable '" + key + "' should be resolved. Parse info stack: \n" + generateParseStack());
    }

    private String label(String key) {
        return "resolve variable " + blue("[") + key + blue("]#" + level());
    }

}
